﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Kokomo.Mirror.CliMetadata.Tables;
using Kokomo.Mirror.CliMetadata.Signatures;
using Kokomo.Mirror.Ia32;
using System.IO;

namespace Kokomo.Mirror.CliMetadata
{
	class CliMethodInfo : CliImplementedMethodInfo, IExtendedMethodInfo
	{
		private MethodDefRow _methodDefRow;
		//private MethodRefSignature _signature;

		public CliMethodInfo(
			MethodDefRow methodDefRow,
			CliMemberBasicInfo basicInfo,
			string name
			)
			: base(basicInfo, name)
		{
			this._methodDefRow = methodDefRow;
			//this._signature = (MethodRefSignature)methodDefRow.Signature.Signature;
			//this._genericParams = new Lazy<Type[]>(this.LoadGenericParams);
			this._genericParams = this.LoadGenericParams();
			//this._body = new Lazy<MethodBody>(this.LoadMethodBody);
		}

		protected internal override MethodDefSignature GetSignature()
		{
			return (MethodDefSignature)this.basicInfo.module.GetSignature(this._methodDefRow.Signature);
		}

		public override MethodAttributes Attributes
		{
			get { return this._methodDefRow.Flags; }
		}

		public override MethodImplAttributes GetMethodImplementationFlags()
		{
			return this._methodDefRow.ImplFlags;
		}

		//private Lazy<MethodBody> _body;
		private MethodBody LoadMethodBody()
		{
			if (this._methodDefRow.ImplFlags.HasFlag(MethodImplAttributes.Native)) return null;

			//Type[] genericTypeArgs = null;
			//if ((object)this.DeclaringType != null)
			//    genericTypeArgs = this.DeclaringType.GetGenericArguments();
			//Type[] genericMethodArgs = this.GetGenericArguments();

			return this.basicInfo.module.LoadMethodBody(
				this,
				(uint)this._methodDefRow.Rva
				//genericTypeArgs,
				//genericMethodArgs
				);
		}
		public override MethodBody GetMethodBody()
		{
			//return this._body.Value;
			return this.LoadMethodBody();
		}
		public override Type ReturnType
		{
			get
			{
				ReturnType returnTypeSignature = this.GetSignature().returnType;

				Type[] genericTypeArguments = null;
				if ((object)this.DeclaringType != null)
					genericTypeArguments = this.DeclaringType.GetGenericArguments();
				Type[] genericMethodArguments = this.GetGenericArguments();

				Type returnType = returnTypeSignature.ResolveType(
					this.basicInfo.module,
					true,
					genericTypeArguments,
					genericMethodArguments
					);

				return returnType;
			}
		}

		#region Generics
		//private Lazy<Type[]> _genericParams;
		private Type[] LoadGenericParams()
		{
			return this.basicInfo.module.QueryGenericParams(this);
		}
		private Type[] _genericParams;

		public override Type[] GetGenericArguments()
		{
			return this._genericParams;
		}
		public override bool ContainsGenericParameters
		{
			get { return this._genericParams.Length > 0; }
		}
		public override bool IsGenericMethod
		{
			get { return this.IsGenericMethodDefinition; }
		}
		public override bool IsGenericMethodDefinition
		{
			get { return this.ContainsGenericParameters; }
		}
		private void ValidateGenericArgument(
			Type argumentType,
			Type parameterType
			)
		{
			if ((object)argumentType == null) throw new ArgumentNullException("argumentType");
			if ((object)parameterType == null) throw new ArgumentNullException("parameterType");

			GenericParameterAttributes paramAttributes = parameterType.GenericParameterAttributes;

			bool isInvalid =
				(paramAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint) && !argumentType.IsClass)
				|| (paramAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint) && !argumentType.IsValueType)
				//|| (paramAttributes.HasFlag(GenericParameterAttributes.DefaultConstructorConstraint) && (argumentType.GetConstructor(Type.EmptyTypes) == null))
				;
			if (isInvalid)
				throw new ArgumentException("Invalid generic type argument");
		}
		public override MethodInfo MakeGenericMethod(params Type[] typeArguments)
		{
			Type[] genericParams = this.GetGenericArguments();
			if (typeArguments.Length != genericParams.Length)
				throw new ArgumentException("Incorrect number of type arguments");

			//for (int i = 0; i < typeArguments.Length; i++)
			//{
			//    this.ValidateGenericArgument(typeArguments[i], genericParams[i]);
			//}

			CliGenericMethodInfo genericMethod = new CliGenericMethodInfo(
				this.basicInfo,
				this,
				this.Name,
				typeArguments
				);

			return genericMethod;
		}
		#endregion

		public uint Rva
		{
			get { return this._methodDefRow.Rva; }
		}
		public NativeProcedureInfo GetNativeImplementation()
		{
			if (!this._methodDefRow.ImplFlags.HasFlag(MethodImplAttributes.Native))
				throw new InvalidOperationException("Must have a native implementation");

			uint rva = this.Rva;
			if (rva == 0) return null;

			return this.basicInfo.module.peModule.GetProcedureAtRva(rva);
		}
	}
}
